package ru.spbu.math.seabattle.server.jms;

import java.util.HashSet;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

import ru.spbu.math.seabattle.client.IClient;
import ru.spbu.math.seabattle.entity.Action;
import ru.spbu.math.seabattle.entity.BijectiveHashMap;
import ru.spbu.math.seabattle.entity.FieldModel;
import ru.spbu.math.seabattle.entity.Game;
import ru.spbu.math.seabattle.entity.GameResult;
import ru.spbu.math.seabattle.entity.PlayerID;
import ru.spbu.math.seabattle.server.IServer;

public class SuperMegaServer implements IServer {

	private final ConcurrentMap<PlayerID, IClient> clientIds = new ConcurrentHashMap<PlayerID, IClient>();
	private final ConcurrentMap<PlayerID, String> clientNames = new ConcurrentHashMap<PlayerID, String>();
	private final BijectiveHashMap<PlayerID, PlayerID> activePlayers = new BijectiveHashMap<PlayerID, PlayerID>();
	private final ConcurrentMap<PlayerID, Game> player2game = new ConcurrentHashMap<PlayerID, Game>();
	private final Set<PlayerID> playerInList = new HashSet<PlayerID>();
	
	private final AtomicLong id = new AtomicLong();
	
	private final String[] SUFFIX_LIST = {
			"Lunohod-1", "Lunohod-2", "Lunohod-3", "Lunohod-4", "Lunohod-5", "Lunohod-6" 
		};
	
	@Override
	public void hello(IClient client, String myName) {

		if(client == null) {
			throw new NullPointerException("client cannot be null!");
		}
		
		while (clientNames.containsValue(myName)) {
			int i = (int) Math.floor(SUFFIX_LIST.length * Math.random());
			myName += "-" + SUFFIX_LIST[i];
		}

		PlayerID givenId = new PlayerID(id.getAndIncrement());
		String givenName = myName;
		
		for (Entry<PlayerID, IClient> entry : clientIds.entrySet()) {
			entry.getValue().onPlayerAddedOrRemoved(true, givenId, givenName);
		}
		
		client.welcome(givenId, givenName);
		
		for (Entry<PlayerID, String> entry : clientNames.entrySet()) {
			client.onPlayerAddedOrRemoved(true, entry.getKey(), entry.getValue());
		}
		
		for (PlayerID id : playerInList) {
			client.onPlayerJoined(id, clientNames.get(id));
		}
		
		clientIds.put(givenId, client);
		clientNames.put(givenId, givenName);
		
		System.out.println("SERVER: added client " + givenId + " with name " + givenName);
	}

	@Override
	public void goodbye(PlayerID myID) {

		if (clientIds.get(myID) == null) {
			throw new IllegalArgumentException("Client with non-existing ID requested"); 
		}
		
		for (Entry<PlayerID, IClient> client : clientIds.entrySet()) {
			client.getValue().onPlayerLeft(myID);
			client.getValue().onPlayerAddedOrRemoved(false, myID, null);
		}
		
		Game game = player2game.get(myID);
		if (game != null) {
			PlayerID opponentId = game.getAnotherPlayer(myID);
			clientIds.get(opponentId).onGameEnded(new GameResult(opponentId, myID, clientNames.get(opponentId), clientNames.get(myID)));
		}
		
		clientIds.remove(myID);
		clientNames.remove(myID);
		
		System.out.println("SERVER: removed client " + myID);
	}
	
	@Override
	public void requestGame(PlayerID myID) {

		playerInList.add(myID);
		System.out.println("SERVER: Game requested from " + clientNames.get(myID));
		Set<Entry<PlayerID, IClient>> players = clientIds.entrySet();
		String name = clientNames.get(myID);
		
		for (Entry<PlayerID, IClient> entry : players) {
			entry.getValue().onPlayerJoined(myID, name);
		}
	}
	
	@Override
	public void withdrawGame(PlayerID myID) {
		
		playerInList.remove(myID);
		System.out.println("SERVER: Game withraw from " + clientNames.get(myID));
		Set<Entry<PlayerID, IClient>> players = clientIds.entrySet();
				
		for (Entry<PlayerID, IClient> entry : players) {
			entry.getValue().onPlayerLeft(myID);
		}
	}
	
	@Override
	public void requestGame(PlayerID myID, PlayerID opponentID) {

		System.out.println("SERVER: " + clientNames.get(opponentID) + " wants to play with " + clientNames.get(myID));
		withdrawGame(opponentID);
		
		IClient opponent = clientIds.get(opponentID);
		
		opponent.onGameRequest(myID, clientNames.get(myID));
	}
	
	@Override
	public void acceptGameRequest(PlayerID myID, PlayerID opponentID) {

		System.out.println("Game began between " + clientNames.get(myID) + " and " + clientNames.get(opponentID));
		withdrawGame(myID);
		
		IClient player = clientIds.get(myID);
		IClient opponent = clientIds.get(opponentID);
		
		activePlayers.put(myID, opponentID);
		
		Game game = new Game();
		game.init(player, myID, opponent, opponentID, clientNames.get(myID), clientNames.get(opponentID));
		
		player2game.put(myID, game);
		player2game.put(opponentID, game);
		
		player.onGameStarted();
		opponent.onGameStarted();
	}
	
	@Override
	public void refuseGameRequest(PlayerID myID, PlayerID opponentID) {
		
		System.out.println("SERVER: " + clientNames.get(opponentID) + " refused to play with " + clientNames.get(myID));
		clientIds.get(myID).onGameRequestRefused(myID);
		requestGame(opponentID);
	}

	@Override
	public void withdrawGame(PlayerID myID, PlayerID opponentID) {
	
		System.out.println("SERVER: " + clientNames.get(opponentID) + " overwanted to play with " + clientNames.get(myID));
		clientIds.get(myID).onGameRequestWithdrawn(opponentID);
		requestGame(myID);
	}

	@Override
	public void makeTurn(PlayerID myID, Action action) {

		System.out.println("SERVER: " + clientNames.get(myID) + " made turn");
		PlayerID opponentID = activePlayers.getByKey(myID);
		if (opponentID == null) {
			opponentID = activePlayers.getByValue(myID);
		}
		
		Game game = player2game.get(myID);
		
		if (game.onTurnMade(myID, action)) {
			activePlayers.removeByKey(myID);
			activePlayers.removeByKey(opponentID);
			player2game.remove(myID);
			player2game.remove(opponentID);
		}
		
	}

	@Override
	public void setField(PlayerID myID, FieldModel field) {
		
		System.out.println("SERVER: " + clientNames.get(myID) + " set his field");
		Game game = player2game.get(myID);
		game.setField(myID, field);
	}
}
